package tech.mhuang.ext.interchan.autoconfiguration.datasecure.advice;

import com.alibaba.fastjson.JSON;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import tech.mhuang.core.util.ObjectUtil;
import tech.mhuang.core.util.StringUtil;
import tech.mhuang.ext.interchan.autoconfiguration.datasecure.DataSecureInfo;
import tech.mhuang.ext.interchan.autoconfiguration.datasecure.DataSecureProperties;
import tech.mhuang.ext.interchan.autoconfiguration.datasecure.annation.DataSecureField;
import tech.mhuang.ext.interchan.autoconfiguration.datasecure.annation.EncryptMapping;
import tech.mhuang.ext.interchan.autoconfiguration.datasecure.consts.EncryptType;
import tech.mhuang.ext.interchan.core.exception.BusinessException;
import tech.mhuang.ext.interchan.protocol.Result;
import tech.mhuang.ext.spring.util.DataUtil;

import java.lang.reflect.Field;

/**
 * 返回数据加密
 *
 * @author mhuang
 * @since 1.6.2
 */
@ControllerAdvice
public class DataSecureResponseBodyAdvice implements ResponseBodyAdvice {

    private DataSecureProperties secretProperties;

    public DataSecureResponseBodyAdvice(DataSecureProperties properties) {
        this.secretProperties = properties;
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return methodParameter.getMethod().isAnnotationPresent(EncryptMapping.class) ||
                methodParameter.getContainingClass().isAnnotationPresent(EncryptMapping.class);
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (ObjectUtil.isEmpty(o)) {
            return null;
        }
        EncryptMapping encryptMapping = methodParameter.getMethod().getAnnotation(EncryptMapping.class);
        if (ObjectUtil.isEmpty(encryptMapping)) {
            encryptMapping = methodParameter.getContainingClass().getAnnotation(EncryptMapping.class);
        }
        DataSecureInfo dataSecureInfo;
        String httpBody;
        String configKey = encryptMapping.value();
        if (StringUtil.isEmpty(configKey)) {
            dataSecureInfo = secretProperties;

        } else if (secretProperties.getDataSecurePropertiesMap().containsKey(configKey)) {
            dataSecureInfo = secretProperties.getDataSecurePropertiesMap().get(configKey);
        } else {
            throw new BusinessException(Result.SYS_FAILD, String.format("找不到配置项%s", configKey));
        }
        if (encryptMapping.type() == EncryptType.ALL) {
            try {
                httpBody = dataSecureInfo.getEncryptDataInterface().newInstance().encrypt(JSON.toJSONString(o), dataSecureInfo.getPublicKey());
            } catch (Exception e) {
                throw new BusinessException(Result.SYS_FAILD, "加密处理异常", e);
            }
        } else {
            //字段加密
            Field[] fields = o.getClass().getDeclaredFields();
            for (Field field : fields) {
                //需要处理的字段
                assignFieldProcess(field, o, dataSecureInfo);
            }
            httpBody = JSON.toJSONString(o);
        }
        return httpBody;
    }

    /**
     * 字段处理
     *
     * @param field          字段
     * @param o              对象
     * @param dataSecureInfo 安全数据
     */
    public void assignFieldProcess(Field field, Object o, DataSecureInfo dataSecureInfo) {
        //TODO 暂不支持对象参数里List、map、对象等转换
        if (field.isAnnotationPresent(DataSecureField.class)) {
            DataSecureField dataSecureField = field.getAnnotation(DataSecureField.class);
            try {
                Object proceeFieldValue = DataUtil.getValueByModelKey(o, field.getName(), field.getType());
                if (dataSecureField.encode() && ObjectUtil.isNotEmpty(proceeFieldValue)) {
                    DataUtil.setValueByModel(o, field.getName(), dataSecureField.Clazz().newInstance().encrypt(
                            proceeFieldValue instanceof String ? (String) proceeFieldValue : JSON.toJSONString(proceeFieldValue),
                            dataSecureInfo.getPublicKey()
                    ));
                }
            } catch (Exception e) {
                throw new BusinessException(Result.SYS_FAILD, "处理字段异常", e);
            }
        }
    }
}
